from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import HttpResponseForbidden, HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.utils.decorators import method_decorator
from django.core import mail
from django.views import View
import re,json
from user.models import User,Address,BrowseHistory
from goods.models import SKU
from tools.response_code import RETCODE
from tools import constants
from django.core.cache import cache
import logging
logger = logging.getLogger('django')

from user.utils import send_email,set_token,get_token

class RegisterView(View):
    '''注册用户'''
    def get(self,request):
        return render(request,'register.html')

    def post(self,request):
        # 正常的通过前端界面验证符合要求的数据，才能在这接收到，前端把所有数据过滤一遍
        # 1、接收数据
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        allow = request.POST.get('allow')
        sms_code = request.POST.get('sms_code')
        #2、验证数据
        if all([username,password,password2,mobile,allow,sms_code]) == False:
            # 有数据为空，不符合要求
            return HttpResponseForbidden('缺少必传参数')
        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return HttpResponseForbidden('请输入5-20个字符的用户名')
        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return HttpResponseForbidden('请输入8-20位的密码')
        # 判断两次密码是否一致
        if password != password2:
            return HttpResponseForbidden('两次输入的密码不一致')
        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseForbidden('请输入正确的手机号码')
        # 判断是否勾选用户协议
        if allow != 'on':
            return HttpResponseForbidden('请勾选用户协议')
        # 验证短信验证码后,发送的验证码和用户填写的验证码是否一直
        sms_code_server = cache.get(f'sms_code{mobile}')
        if sms_code_server != sms_code:
            return render(request,'register.html',{'errmsg':'输入验证码有误'})
        # 3、保存数据
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except Exception as e:
            logger.error(e)
            return render(request, 'register.html', {'register_errmsg': '注册失败'})
        # 4、保存用户的登录状态 14天
        login(request,user)
        # 5、响应页面 同时写入cookie
        # TODO : 重定向到首页
        response = redirect('contents:index')
        response.set_cookie('username',user.username,max_age=3600*24*14)
        return

class CheckUsernameView(View):
    # 检查用户名是否重复
    '''接收前端发送的ajax请求，返回count 给前端'''
    def get(self,request,username):
        count = User.objects.filter(username=username).count()
        return JsonResponse({'count':count,'code':RETCODE.OK,'errmsg':'ok'})


class CheckMobileView(View):
    def get(self,request,mobile):
        count = User.objects.filter(mobile=mobile).count()
        return JsonResponse({'count': count, 'code': RETCODE.OK, 'errmsg': 'ok'})


class LoginView(View):
    # 登录的类视图
    _next = ''

    def get(self,request):
        LoginView._next = request.GET.get('next')
        return render(request,'login.html')

    def post(self,request):
        # 获取数据
        username= request.POST.get('username')
        password= request.POST.get('password')
        remembered= request.POST.get('remembered')
        # 判断username password 是否为空
        if not username or not password:
            return redirect('user:login')
        # 数据库中比对用户名和密码是否存在 返回一个用户对象或者是一个None
        user = authenticate(username= username,password=password)
        if user is None:
            # 用户名或者密码不正确
            # flag = True
            # msg_err = '用户名密码错误'
            return render(request,'login.html',locals())
        login(request,user)
        if remembered is None:
            # 不记住登录状态，那就让关闭浏览器后，sessionid就过期了，过期了，下一次登录这个网站就需要重新登录
            request.session.set_expiry(0)
        else:
            request.session.set_expiry(None) # 如果设置None 就是使用django 默认的session过期时间
        # 如果有next 则跳转到next 地址 ，如果没有跳转到首页

        response = redirect(LoginView._next or 'contents:index')
        response.set_cookie('username',user.username,max_age=3600*24*14)
        return response


class LogoutView(View):
    # 退出登录
    def get(self,request):
        logout(request) # 删除sesstion
        #再删除cookie 里的记录的username
        response = redirect('user:login')
        response.delete_cookie('username')
        # 响应重定向到登录页面
        return response

@method_decorator(login_required,name='get')
class InfoView(View):
    # 用户中心
    def get(self,request):
        return render(request,'user_center_info.html')

class SaveEmailView(View):
    '''保存邮箱的一个类视图'''
    def put(self,request):
        # 获取数据，与之前get post 获取的方式不同，request.body.deconde()
        json_data = request.body.decode() #  '{"email":"110703203@qq.com"}' json
        email = json.loads(json_data)['email']
        # email 数据验证
        if not email:
            return JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少email参数'})
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return JsonResponse({'code': RETCODE.EMAILERR, 'errmsg': '邮箱格式错误'})
        # 获取一下当前请求的用户，将邮箱保存到对应用户的email字段中
        user = request.user
        user.email = email
        user.save()
        # 发送邮件
        subject = '验证激活邮箱'
        # 通过我们utils模块中自定义的加密方法生成一个加密的token
        token = set_token(user.id)
        verify_url = f'http://127.0.0.1:8000/emails/verification/?token={token}'
        message = f'''
            <p>尊敬的{user.username} 用户您好，欢迎使用美多商城邮箱激活验证服务，请点击下面的链接，验证激活邮箱：</p>
            <a href="{verify_url}">{verify_url}<a>
        '''
        send_email(email,subject,message)
        # 响应添加邮箱结果
        return JsonResponse({'code': RETCODE.OK, 'errmsg': '添加邮箱成功'})

class VerifyEmailView(View):
    # 用户点击链接 激活邮箱
    def get(self,request):
        # 获取数据 ，查询字符串
        token = request.GET.get('token')
        # 解密token
        uid = get_token(token)
        # 通过uid 去数据库中查找对应的用户，然后将他的email_active 设置为True
        User.objects.filter(id = uid).update(email_active=True)
        # 写入日志
        logger.info(f'id为{uid}用户，邮箱已激活')
        return redirect('user:info')

class AddressesView(View):
    def get(self,request):
        """展示收货地址界面"""
        # 获取用户地址列表
        login_user = request.user
        addresses = Address.objects.filter(user=login_user, is_deleted=False)
        address_dict_list = []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
            address_dict_list.append(address_dict)


        default_address_id = login_user.default_address_id
        addresses = address_dict_list

        return render(request,'user_center_site.html',locals())

class AddressesCreateView(View):
    '''新增收货地址 '''
    def post(self,request):
        # 判断是否超过地址上限：最多20个
        # Address.objects.filter(user=request.user).count()
        count = request.user.addresses.count()
        if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
            return JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})

        # 接收参数
        # 因为前端使用json 传递过来的数据，是一个二进制，decode()将二进制转换为字符串 ，
        # json.loads() 将一个json 格式的字符串转换为phthon 的数据类型 ，一个字典
        # json.dumps() 将一个phthon 的数据类型转换为json 格式的字符串 ，
        json_dict = json.loads(request.body.decode())

        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')
        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return HttpResponseForbidden('参数email有误')
                # 保存地址信息
        try:
            address = Address.objects.create(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )

            # 设置默认地址
            if not request.user.default_address:
                request.user.default_address = address
                request.user.save()
        except Exception as e:
            logger.error(e)
            return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})

        # 新增地址成功，将新增的地址响应给前端实现局部刷新
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # 响应保存结果
        return JsonResponse({'code': RETCODE.OK, 'errmsg': '新增地址成功', 'address': address_dict})


class UpdateDeleteAddressesView(View):
    def put(self,request,address_id):
        '''修改地址的函数'''
        # 接收数据
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')
        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return HttpResponseForbidden('参数email有误')
                # 保存地址信息
        try:
            Address.objects.filter(id = address_id).update(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except Exception as e:
            logger.error(e)
            return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '修改地址失败'})

        # 修改地址成功，将新增的地址响应给前端实现局部刷新
        address = Address.objects.get(id = address_id)
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # 响应保存结果
        return JsonResponse({'code': RETCODE.OK, 'errmsg': '修改地址成功', 'address': address_dict})


    def delete(self,request,address_id):
        '''删除地址的函数'''
        # 通过address_id 在数据库中查出这条记录，删除
        try:
            addr = Address.objects.get(id = address_id)
        except Address.DoesNotExist as e:
            logger.error(e)
            return JsonResponse({'code':RETCODE.DBERR, 'errmsg': '地址id不正确'})
        else:
            addr.is_deleted = True
            addr.save()
            # 返回相应
            return JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})


class DefaultAddressesView(View):
    '''设置默认收货地址'''
    def put(self,request,address_id):
       user = request.user
       # 给外键赋值，第一种方法，就是直接给外键一个实实在在的对象
       # addr = Address.objects.get(id = address_id)
       # user.default_address = addr
       # 第二种方法，可以给外键_id = 真实对象的主键 也就是对象的id
       user.default_address_id = address_id
       user.save()
       return JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})



class AddressesTitleView(View):
    '''设置地址标题'''
    def put(self,request,address_id):
        # 获取前端传来的title
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        # 通过address_id 查找到对应的 这条地址记录
        try:
            addr = Address.objects.get(id = address_id)
        except Address.DoesNotExist as e:
            logger.error(e)
            return JsonResponse({'code':RETCODE.DBERR, 'errmsg': '地址id不正确'})
        else:
        # 将这条地址记录的title = 前端传来的title save（）
            addr.title = title
            addr.save()
            return JsonResponse({'code': RETCODE.OK, 'errmsg': '设置地址标题成功'})


class ChangePasswordView(View):
    '''修改用户密码'''
    def get(self,request):
        return render(request,'user_center_pass.html')

    def post(self,request):
        # 获取前端表单提交的数据
        old_pwd = request.POST.get('old_pwd')
        new_pwd = request.POST.get('new_pwd')
        new_cpwd = request.POST.get('new_cpwd')
        # 验证数据
        if not all([old_pwd,new_pwd,new_cpwd]):
            # 判断三个值是否为空，如果为空，则返回响应
            return HttpResponseForbidden('缺少必要的参数')
        if not request.user.check_password(old_pwd):
            # 输入的密码跟数据库中对应用户的密码不匹配
            return redirect('user:change_password')
        if not re.match(r'^[0-9a-zA-Z]{8,20}$',new_pwd):
            # 输入的密码不符合要求
            return HttpResponseForbidden('密码不符合要求')
        if new_pwd != new_cpwd:
            # 两次密码不一致
            return HttpResponseForbidden('两次密码不一致')
        # 保存密码
        request.user.set_password(new_pwd)
        request.user.save()
        # User.objects.filter(username= request.user.username).update(password=new_pwd)

        # 重新登录，并且修改登录状态
        logout(request)
        # 删除cookie 是需要用响应response
        response = redirect('user:login')
        response.delete_cookie('username')

        return response


class BrowseHistoryView(View):
    '''用户浏览记录'''
    def get(self,request):
        # 去数据库中查找所有的浏览记录,并且排序，截取前五个
        historys = BrowseHistory.objects.filter(user=request.user).order_by('-update_time')[:5]
        # 再重新封装一个字典返回给前端
        skus = []
        for history in historys:
            sku = SKU.objects.get(id = history.sku_id) # sku表中的商品
            skus.append({
                'id':sku.id,
                'name':sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price
            })

        return JsonResponse({'code':RETCODE.OK,'errmsg':'ok','skus':skus})

    def post(self,request):
        # 要求登录用户才能保存浏览记录，先要判断用户是否登录
        user= request.user
        if not user.is_authenticated:
            return HttpResponseForbidden('未登录用户不能记录浏览记录')
        # 获取数据 sku_id
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        # 判断一下 sku_id 是否正确
        if not sku_id:
            return HttpResponseForbidden('参数不存在')
        is_exist = SKU.objects.filter(id = sku_id).exists()
        if not is_exist:
            # 说明sku_id 不正确
            return HttpResponseForbidden('参数不存在')
        # 去重，如果这个用户曾经浏览过这个商品，不用添加了，修改一下sku_id
        # 保存数据 get_or_create()
        # get_or_create() 如果查询到就返回一个对象，如果没有就创建一个对象
        history,created = BrowseHistory.objects.get_or_create(user=user,sku_id=sku_id)
        if not created:
            history.sku_id = sku_id
            history.save()

        # 排序 按照更新时间排序
        return JsonResponse({'code':RETCODE.OK,'errmsg':'ok'})

from orders.models import OrderInfo,OrderGoods

class OrderInfoView(View):
    '全部订单页面'
    def get(self,request,page_num):
        # 查询当前用户的所有的订单
        user = request.user
        # 用户跟订单是一对多的关系，使用当前用户.orderinfo_set 这个隐藏属性去做反向查询
        orders = user.orderinfo_set.all().order_by('-create_time')
        # 遍历订单
        for order in orders:
            #ORDER_STATUS_CHOICES是一个二维的元组，我们通过下标获取status对应的值，需要通过两个下标才能获取到
            # 订单的状态名
            order.status_name = OrderInfo.ORDER_STATUS_CHOICES[order.status -1][1]
            # 订单的购买方式名
            order.pay_method_name = OrderInfo.PAY_METHOD_CHOICES[order.pay_method -1][1]
            # 给订单增加一个属性，当前订单下的所有商品
            order.sku_list = []
            # 当前订单下的所有商品
            order_goods = order.skus.all()
            # for order_good in order_goods:
            #     # sku 是当前订单商品表中的商品对应的sku表中的真实商品，
            #     sku = order_good.sku
            #     # 给sku 绑定一个属性 ：数量 其实就是当前订单商品的数量
            #     sku.count = order_good.count
            #     # 再给sku绑定一个属性： 小计：数量*价格
            #     sku.amount = order_good.count * sku.price
            #     order.sku_list.append(sku)
            for order_good in order_goods:
                sku = order_good.sku
                order.sku_list.append({
                    'default_image':f'/static/images/goods/{sku.default_image.url}.jpg',
                    'name':sku.name,
                    'price':sku.price,
                    'count':order_good.count,
                    'amount':order_good.count * sku.price
                })
            # 分页
            # 通过分页的类实例化一个分页对象
            paginator = Paginator(orders,3)
            # 再通过分页对象的page()方法，生成一个当前页的对象
            page_orders = paginator.page(page_num)
            # 一共有多少页
            total_page = paginator.num_pages
        return render(request,'user_center_order.html',locals())
